import re
from enum import Enum
import tkinter.ttk as ttk
import gettext
import datetime
import locale

_ = gettext.gettext


class TextFormat(Enum):
    BINARY = 1
    OCTAL = 2
    DECIMAL = 3
    HEXADECIMAL = 4
    ASCII = 5
    UTF8 = 6


def supported_text_formats() -> list:
    return [
        TextFormat.BINARY.value,
        TextFormat.OCTAL.value,
        TextFormat.DECIMAL.value,
        TextFormat.HEXADECIMAL.value,
        TextFormat.ASCII.value,
        TextFormat.UTF8.value,
    ]


def text_format_string_from_type(text_format: int) -> str:
    if text_format == TextFormat.BINARY.value:
        return _("Binary")
    elif text_format == TextFormat.OCTAL.value:
        return _("Octal")
    elif text_format == TextFormat.DECIMAL.value:
        return _("Decimal")
    elif text_format == TextFormat.HEXADECIMAL.value:
        return _("Hexadecimal")
    elif text_format == TextFormat.ASCII.value:
        return "ASCII"
    elif text_format == TextFormat.UTF8.value:
        return "UTF-8"
    else:
        raise ValueError("Invalid text format type")


def text_format_type_from_string(text_format: str) -> int:
    if text_format == _("Binary"):
        return TextFormat.BINARY.value
    elif text_format == _("Octal"):
        return TextFormat.OCTAL.value
    elif text_format == _("Decimal"):
        return TextFormat.DECIMAL.value
    elif text_format == _("Hexadecimal"):
        return TextFormat.HEXADECIMAL.value
    elif text_format == "ASCII":
        return TextFormat.ASCII.value
    elif text_format == "UTF-8":
        return TextFormat.UTF8.value
    else:
        raise ValueError("Invalid text format string")


def string_to_binary_array(data: str, text_format: int) -> bytes:
    if text_format == TextFormat.BINARY.value:
        # "00000001 00000002 00000008" -> b'\x01\x02\x08'
        return bytes([int(x, 2) for x in data.split()])
    elif text_format == TextFormat.OCTAL.value:
        # "001 002 003" -> b'\x01\x02\x03'
        return bytes([int(x, 8) for x in data.split()])
    elif text_format == TextFormat.DECIMAL.value:
        # "1 2 3" -> b'\x01\x02\x03'
        return bytes([int(x) for x in data.split()])
    elif text_format == TextFormat.HEXADECIMAL.value:
        # "01 02 03" -> b'\x01\x02\x03'
        return bytes([int(x, 16) for x in data.split()])
    elif text_format == TextFormat.ASCII.value:
        return data.encode("ascii")
    elif text_format == TextFormat.UTF8.value:
        return data.encode("utf-8")
    else:
        raise ValueError("Invalid text format type")


def setup_text_format_for_combobox(combo_box: ttk.Combobox):
    formats = supported_text_formats()
    format_names = [text_format_string_from_type(f) for f in formats]
    combo_box["values"] = format_names
    combo_box.current(4)
    combo_box.state(["readonly"])


def validate_binary_input(new_value: str):
    if new_value == "":
        return True

    # new_value must like "00000001 10000001 00011001"
    ret = re.fullmatch(r"^([01]{1,8}( [01]{1,8})* ?)$", new_value)
    return ret is not None and ret != ""


def validate_octal_input(new_value: str):
    if new_value == "":
        return True

    """
    # Regular expression pattern
    pattern = r"^(?:[0-3]?[0-7]{1,2}|[0-7]{1,2})( (?:[0-3]?[0-7]{1,2}|[0-7]{1,2}))* ?$"
    
    # Test the pattern
    test_strings = [
        "1 23 377 ",  # Valid with trailing space
        "123 456 7",  # Invalid (456 exceeds 377 in octal)
        "1 2 3",  # Valid
        "12 34 56",  # Valid
        "123",  # Valid
        "123 377",  # Valid
        "123 377 ",  # Valid with trailing space
        "123 377  ",  # Invalid (multiple trailing spaces)
    ]
    
    for s in test_strings:
        match = re.fullmatch(pattern, s)
        print(f"'{s}': {'Valid' if match else 'Invalid'}")
    """

    # new_value must like "001 002 003"
    ret = re.fullmatch(
        r"^(?:[0-3]?[0-7]{1,2}|[0-7]{1,2})( (?:[0-3]?[0-7]{1,2}|[0-7]{1,2}))* ?$",
        new_value,
    )
    return ret is not None and ret != ""


def validate_decimal_input(new_value: str):
    if new_value == "":
        return True

    """
    # Regular expression pattern
    pattern = r"^(?:[0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])( (?:[0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5]))* ?$"

    # Test the pattern
    test_strings = [
        "1 23 255 ",  # Valid with trailing space
        "123 456 7",  # Invalid (456 exceeds 255)
        "1 2 3",  # Valid
        "12 34 56",  # Valid
        "123",  # Valid
        "123 255",  # Valid
        "123 255 ",  # Valid with trailing space
        "123 255  ",  # Invalid (multiple trailing spaces)
    ]

    for s in test_strings:
        match = re.fullmatch(pattern, s)
        print(f"'{s}': {'Valid' if match else 'Invalid'}")
    """

    ret = re.fullmatch(
        r"^(?:[0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])( (?:[0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5]))* ?$",
        new_value,
    )
    return ret is not None and ret != ""


def validate_hexadecimal_input(new_value: str):
    if new_value == "":
        return True

    # new_value must like "01 02 03"
    ret = re.fullmatch(r"^([0-9a-fA-F]{1,2}( [0-9a-fA-F]{1,2})* ?)$", new_value)
    return ret is not None and ret != ""


def validate_ascii_input(new_value: str):
    if new_value == "":
        return True

    # new_value must like "abc..."
    ret = re.fullmatch(r"([ -~])*", new_value)
    return ret is not None and ret != ""


def validate_utf8_input(new_value: str):
    # Do something make PyCharm happy
    if new_value == "":
        return True

    return True


def setup_text_format_for_entry(entry: ttk.Entry, text_format: int):
    if entry is None:
        raise ValueError("Entry is None")

    entry.delete(0, "end")

    if text_format == TextFormat.BINARY.value:
        validation = (entry.register(validate_binary_input), "%P")
        entry.config(validate="key", validatecommand=validation)
    elif text_format == TextFormat.OCTAL.value:
        validation = (entry.register(validate_octal_input), "%P")
        entry.config(validate="key", validatecommand=validation)
    elif text_format == TextFormat.DECIMAL.value:
        validation = (entry.register(validate_decimal_input), "%P")
        entry.config(validate="key", validatecommand=validation)
    elif text_format == TextFormat.HEXADECIMAL.value:
        validation = (entry.register(validate_hexadecimal_input), "%P")
        entry.config(validate="key", validatecommand=validation)
    elif text_format == TextFormat.ASCII.value:
        validation = (entry.register(validate_ascii_input), "%P")
        entry.config(validate="key", validatecommand=validation)
    elif text_format == TextFormat.UTF8.value:
        validation = (entry.register(validate_utf8_input), "%P")
        entry.config(validate="key", validatecommand=validation)
    else:
        entry.config(validate="none", validatecommand=None)


def binary_bytes_to_string(data: bytes, text_format: int) -> str:
    if text_format == TextFormat.BINARY.value:
        return " ".join([f"{x:08b}" for x in data])
    elif text_format == TextFormat.OCTAL.value:
        return " ".join([f"{x:03o}" for x in data])
    elif text_format == TextFormat.DECIMAL.value:
        return " ".join([f"{x}" for x in data])
    elif text_format == TextFormat.HEXADECIMAL.value:
        return " ".join([f"{x:02X}" for x in data])
    elif text_format == TextFormat.ASCII.value:
        try:
            return data.decode("ascii")
        except UnicodeDecodeError:
            return f"(Invalid ASCII:{binary_bytes_to_string(data, TextFormat.HEXADECIMAL.value)})"
    elif text_format == TextFormat.UTF8.value:
        try:
            return data.decode("utf-8")
        except UnicodeDecodeError:
            return f"(Invalid utf-8:{binary_bytes_to_string(data, TextFormat.HEXADECIMAL.value)})"
    else:
        raise "Invalid text format type"


def get_local_date_format() -> str:
    locale.setlocale(locale.LC_TIME, "")
    current_date = datetime.datetime.now()
    return current_date.strftime("%x")


def current_date_string() -> str:
    # get date string with loaded date format
    date_format_string = get_local_date_format()
    return datetime.datetime.now().strftime(date_format_string)


def get_local_time_format() -> str:
    locale.setlocale(locale.LC_TIME, "")
    current_time = datetime.datetime.now()
    return current_time.strftime("%X")


def current_time_string() -> str:
    # get time string with loaded time format
    time_format_string = get_local_time_format()
    return datetime.datetime.now().strftime(time_format_string)


def current_millis_string() -> str:
    # get time string with loaded time format
    return datetime.datetime.now().strftime("%f")
